{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 投资组合 Portfolio"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备代码 Prepare"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np # 数据处理最重要的模块\n",
    "import pandas as pd # 数据处理最重要的模块\n",
    "import scipy.stats as stats # 统计模块\n",
    "import scipy\n",
    "# import pymysql  # 导入数据库模块\n",
    "\n",
    "from datetime import datetime # 时间模块\n",
    "import statsmodels.formula.api as smf  # OLS regression\n",
    "\n",
    "# import pyreadr # read RDS file\n",
    "\n",
    "from matplotlib import style\n",
    "import matplotlib.pyplot as plt  # 画图模块\n",
    "import matplotlib.dates as mdates\n",
    "\n",
    "from matplotlib.font_manager import FontProperties # 作图中文\n",
    "from pylab import mpl\n",
    "#mpl.rcParams['font.sans-serif'] = ['SimHei']\n",
    "#plt.rcParams['font.family'] = 'Times New Roman'\n",
    "\n",
    "#输出矢量图 渲染矢量图\n",
    "%matplotlib inline\n",
    "%config InlineBackend.figure_format = 'svg'\n",
    "\n",
    "from IPython.core.interactiveshell import InteractiveShell # jupyter运行输出的模块\n",
    "#显示每一个运行结果\n",
    "InteractiveShell.ast_node_interactivity = 'all'\n",
    "\n",
    "#设置行不限制数量\n",
    "#pd.set_option('display.max_rows',None)\n",
    "\n",
    "#设置列不限制数量\n",
    "pd.set_option('display.max_columns', None)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 投资组合的构造方法"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这一节，我们主要关注的问题是在个股层面。换句话说，就是我们能否预测个股的收益率。从计量层面上来说，就是是否能够找到某个指标能够在横截面预测个股的超额收益率（大家注意，以后我们如果不特别说明和产生歧义的情况下，收益率都表示的是减去无风险利率的超额收益率 excess return）。\n",
    "\n",
    "那么，问题是，我们怎么去寻找这样的指标呢，哪些指标能够预测股票的超额收益率呢，预测的能力或者结果如何呢，这些都是我们要去思考以及数据处理的工作。\n",
    "\n",
    "那么，标准的预测收益率的方法啊是使用构造投资组合的方法，使用零成本投资的策略，检验是否在此条件下仍然能够获得收益率。\n",
    "\n",
    "- 指标的选取：理论？实务？\n",
    "- 避免前视偏误（look ahead bias）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 细节"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "具体而言\n",
    "\n",
    "我们要使用t−1\n",
    "月的指标对股票进行分组（一般分为10组），在不同的分组中，计算组内股票t\n",
    "月的加权平均收益率（或者简单平均收益率）\n",
    "使用分组中最低组和最高组的差作为我们的投资组合，例如我们买入最高组的股票同时卖空最低组的股票（或者买入最低组卖空最高组，这个视指标和下一期收益率关系而定），这样我们自己的成本是零，但是我们可以通过这样的零成本，获得很高的收益\n",
    "一般，我们将这种策略和大盘（市场）收益率进行对比，或者是Fama三因子、五因子等标准的定价因子对比。\n",
    "\n",
    "在策略构造过程中，却有很多数据处理上的技术难题。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "ename": "FileNotFoundError",
     "evalue": "[Errno 2] No such file or directory: 'datasets/ret_mon_python2023.csv'",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mFileNotFoundError\u001b[0m                         Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[2], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m cross \u001b[38;5;241m=\u001b[39m pd\u001b[38;5;241m.\u001b[39mread_csv(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mdatasets/ret_mon_python2023.csv\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[0;32m      2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mtseries\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01moffsets\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m MonthEnd\n\u001b[0;32m      3\u001b[0m cross[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmonth\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;241m=\u001b[39m pd\u001b[38;5;241m.\u001b[39mto_datetime(cross[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmonth\u001b[39m\u001b[38;5;124m'\u001b[39m], \u001b[38;5;28mformat\u001b[39m\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m%\u001b[39m\u001b[38;5;124mY-\u001b[39m\u001b[38;5;124m%\u001b[39m\u001b[38;5;124mm-\u001b[39m\u001b[38;5;132;01m%d\u001b[39;00m\u001b[38;5;124m'\u001b[39m) \u001b[38;5;241m+\u001b[39m MonthEnd(\u001b[38;5;241m1\u001b[39m)\n",
      "File \u001b[1;32mc:\\Users\\21230\\anaconda3\\Lib\\site-packages\\pandas\\io\\parsers\\readers.py:948\u001b[0m, in \u001b[0;36mread_csv\u001b[1;34m(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options, dtype_backend)\u001b[0m\n\u001b[0;32m    935\u001b[0m kwds_defaults \u001b[38;5;241m=\u001b[39m _refine_defaults_read(\n\u001b[0;32m    936\u001b[0m     dialect,\n\u001b[0;32m    937\u001b[0m     delimiter,\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m    944\u001b[0m     dtype_backend\u001b[38;5;241m=\u001b[39mdtype_backend,\n\u001b[0;32m    945\u001b[0m )\n\u001b[0;32m    946\u001b[0m kwds\u001b[38;5;241m.\u001b[39mupdate(kwds_defaults)\n\u001b[1;32m--> 948\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m _read(filepath_or_buffer, kwds)\n",
      "File \u001b[1;32mc:\\Users\\21230\\anaconda3\\Lib\\site-packages\\pandas\\io\\parsers\\readers.py:611\u001b[0m, in \u001b[0;36m_read\u001b[1;34m(filepath_or_buffer, kwds)\u001b[0m\n\u001b[0;32m    608\u001b[0m _validate_names(kwds\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnames\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m))\n\u001b[0;32m    610\u001b[0m \u001b[38;5;66;03m# Create the parser.\u001b[39;00m\n\u001b[1;32m--> 611\u001b[0m parser \u001b[38;5;241m=\u001b[39m TextFileReader(filepath_or_buffer, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwds)\n\u001b[0;32m    613\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m chunksize \u001b[38;5;129;01mor\u001b[39;00m iterator:\n\u001b[0;32m    614\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m parser\n",
      "File \u001b[1;32mc:\\Users\\21230\\anaconda3\\Lib\\site-packages\\pandas\\io\\parsers\\readers.py:1448\u001b[0m, in \u001b[0;36mTextFileReader.__init__\u001b[1;34m(self, f, engine, **kwds)\u001b[0m\n\u001b[0;32m   1445\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39moptions[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhas_index_names\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m kwds[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhas_index_names\u001b[39m\u001b[38;5;124m\"\u001b[39m]\n\u001b[0;32m   1447\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandles: IOHandles \u001b[38;5;241m|\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m-> 1448\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_engine \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_make_engine(f, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mengine)\n",
      "File \u001b[1;32mc:\\Users\\21230\\anaconda3\\Lib\\site-packages\\pandas\\io\\parsers\\readers.py:1705\u001b[0m, in \u001b[0;36mTextFileReader._make_engine\u001b[1;34m(self, f, engine)\u001b[0m\n\u001b[0;32m   1703\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mb\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m mode:\n\u001b[0;32m   1704\u001b[0m         mode \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mb\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m-> 1705\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandles \u001b[38;5;241m=\u001b[39m get_handle(\n\u001b[0;32m   1706\u001b[0m     f,\n\u001b[0;32m   1707\u001b[0m     mode,\n\u001b[0;32m   1708\u001b[0m     encoding\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39moptions\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mencoding\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m),\n\u001b[0;32m   1709\u001b[0m     compression\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39moptions\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcompression\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m),\n\u001b[0;32m   1710\u001b[0m     memory_map\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39moptions\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmemory_map\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mFalse\u001b[39;00m),\n\u001b[0;32m   1711\u001b[0m     is_text\u001b[38;5;241m=\u001b[39mis_text,\n\u001b[0;32m   1712\u001b[0m     errors\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39moptions\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mencoding_errors\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mstrict\u001b[39m\u001b[38;5;124m\"\u001b[39m),\n\u001b[0;32m   1713\u001b[0m     storage_options\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39moptions\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mstorage_options\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m),\n\u001b[0;32m   1714\u001b[0m )\n\u001b[0;32m   1715\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandles \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m   1716\u001b[0m f \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandles\u001b[38;5;241m.\u001b[39mhandle\n",
      "File \u001b[1;32mc:\\Users\\21230\\anaconda3\\Lib\\site-packages\\pandas\\io\\common.py:863\u001b[0m, in \u001b[0;36mget_handle\u001b[1;34m(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)\u001b[0m\n\u001b[0;32m    858\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(handle, \u001b[38;5;28mstr\u001b[39m):\n\u001b[0;32m    859\u001b[0m     \u001b[38;5;66;03m# Check whether the filename is to be opened in binary mode.\u001b[39;00m\n\u001b[0;32m    860\u001b[0m     \u001b[38;5;66;03m# Binary mode does not support 'encoding' and 'newline'.\u001b[39;00m\n\u001b[0;32m    861\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m ioargs\u001b[38;5;241m.\u001b[39mencoding \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mb\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m ioargs\u001b[38;5;241m.\u001b[39mmode:\n\u001b[0;32m    862\u001b[0m         \u001b[38;5;66;03m# Encoding\u001b[39;00m\n\u001b[1;32m--> 863\u001b[0m         handle \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mopen\u001b[39m(\n\u001b[0;32m    864\u001b[0m             handle,\n\u001b[0;32m    865\u001b[0m             ioargs\u001b[38;5;241m.\u001b[39mmode,\n\u001b[0;32m    866\u001b[0m             encoding\u001b[38;5;241m=\u001b[39mioargs\u001b[38;5;241m.\u001b[39mencoding,\n\u001b[0;32m    867\u001b[0m             errors\u001b[38;5;241m=\u001b[39merrors,\n\u001b[0;32m    868\u001b[0m             newline\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m    869\u001b[0m         )\n\u001b[0;32m    870\u001b[0m     \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m    871\u001b[0m         \u001b[38;5;66;03m# Binary mode\u001b[39;00m\n\u001b[0;32m    872\u001b[0m         handle \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mopen\u001b[39m(handle, ioargs\u001b[38;5;241m.\u001b[39mmode)\n",
      "\u001b[1;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'datasets/ret_mon_python2023.csv'"
     ]
    }
   ],
   "source": [
    "cross = pd.read_csv('datasets/ret_mon_python2023.csv')\n",
    "from pandas.tseries.offsets import MonthEnd\n",
    "cross['month'] = pd.to_datetime(cross['month'], format='%Y-%m-%d') + MonthEnd(1)\n",
    "# 补齐股票代码 如果不满6位 在前面补上0\n",
    "cross['Stkcd'] = cross['Stkcd'].apply(lambda x: '{:0>6}'.format(x)) # 6位股票代码\n",
    "cross['w'] = 1\n",
    "cross = cross.dropna(subset=['ret'])\n",
    "cross"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据细节"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- floatingvalue 为股票的月末流通市值\n",
    "- totalvalue 为股票月末的总市值\n",
    "- Return 为股票该月的考虑红利的收益率\n",
    "- rfmonth 为该月的无风险收益率\n",
    "- Rank 为该公司上市的第几个月份\n",
    "- Freq 为该股票在该月的交易天数\n",
    "- ret = Return - rfmonth\n",
    "- next_ret 为该股票下一个月的ret\n",
    "- sizef 为floatingvalue的自然对数\n",
    "- sizet 为totalvalue的自然对数\n",
    "- ew 为1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 策略细节"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fenweishu = pd.DataFrame(\n",
    "    cross.groupby(['month'])['ret'].quantile([0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]))\n",
    "fenweishu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fenweishu = fenweishu.reset_index()\n",
    "fenweishu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fenweishu = fenweishu.pivot_table(index='month',columns='level_1',values='ret')\n",
    "fenweishu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Don't run\n",
    "# fenweishu = fenweishu.reset_index()\n",
    "# fenweishu\n",
    "# Don't run\n",
    "# 打印数据表列名\n",
    "# fenweishu.columns\n",
    "# Don't run\n",
    "# fenweishu 表格宽变长\n",
    "# fenweishu = fenweishu.melt(id_vars=['month'],value_vars=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9])\n",
    "# fenweishu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fenweishu.columns = ['one','two','three','four','five','six','seven','eight','nine']\n",
    "fenweishu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "portfolio = pd.merge(cross,fenweishu,on='month',how='left')\n",
    "portfolio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "portfolio['sort'] = np.where(\n",
    "    portfolio['ret'] <= portfolio['one'], 'P1',\n",
    "    np.where(\n",
    "        portfolio['ret'] <= portfolio['two'], 'P2',\n",
    "        np.where(\n",
    "            portfolio['ret'] <= portfolio['three'], 'P3',\n",
    "            np.where(\n",
    "                portfolio['ret'] <= portfolio['four'], 'P4',\n",
    "                np.where(\n",
    "                    portfolio['ret'] <= portfolio['five'], 'P5',\n",
    "                    np.where(\n",
    "                        portfolio['ret'] <= portfolio['six'], 'P6',\n",
    "                        np.where(\n",
    "                            portfolio['ret'] <= portfolio['seven'], 'P7',\n",
    "                            np.where(\n",
    "                                portfolio['ret'] <= portfolio['eight'], 'P8',\n",
    "                                np.where(\n",
    "                                    portfolio['ret'] <= portfolio['nine'],\n",
    "                                    'P9', 'Pmax')))))))))\n",
    "portfolio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "portfolio = portfolio.dropna(subset=['next_ret','floatingvalue'])\n",
    "portfolio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "portfolio_mom =  pd.DataFrame(\n",
    "    portfolio.groupby(['month','sort']).apply(lambda x: np.average(x['next_ret'],weights = x['floatingvalue']),include_groups=False))\n",
    "portfolio_mom"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "portfolio_mom = portfolio_mom.reset_index()\n",
    "portfolio_mom.columns = ['month', 'sort', 'p']\n",
    "portfolio_mom"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Month plus one month \n",
    "portfolio_mom['month'] = portfolio_mom['month'] + MonthEnd(1)\n",
    "# drop NA\n",
    "portfolio_mom = portfolio_mom.dropna()\n",
    "portfolio_mom"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "portfolio_mom = portfolio_mom.pivot_table(index='month',\n",
    "                                                    columns='sort',\n",
    "                                                    values='p')\n",
    "# long lowest return stocks and short highest return stocks\n",
    "# 每个月持有最低换手率的10%股票，卖空最高换手率的10%股票\n",
    "portfolio_mom['My_portfolio'] = portfolio_mom['P1'] - portfolio_mom['Pmax']\n",
    "portfolio_mom"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "portfolio_mom = portfolio_mom['1995-01':'2023-12']\n",
    "portfolio_mom"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_port = smf.ols('My_portfolio ~ 1',\n",
    "                 data=portfolio_mom['2000-02':'2023-12']).fit(\n",
    "                     cov_type='HAC', cov_kwds={'maxlags': 6})\n",
    "print(model_port.summary())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.read_csv('datasets/000001.csv')\n",
    "data['Day'] = pd.to_datetime(data['Day'],format='%Y/%m/%d')\n",
    "data.set_index('Day', inplace = True)\n",
    "data.sort_values(by = ['Day'],axis=0, ascending=True)\n",
    "data_new = data['1995-01':'2024-09'].copy()\n",
    "data_new['Close'] = pd.to_numeric(data_new['Close'])\n",
    "data_new['Preclose'] = pd.to_numeric(data_new['Preclose'])\n",
    "data_new['Raw_return'] = data_new['Close'] / data_new['Preclose'] - 1\n",
    "Month_data = data_new.resample('ME')['Raw_return'].apply(lambda x: (1+x).prod() - 1).to_frame()\n",
    "Month_data.reset_index(inplace = True)\n",
    "Month_data.rename(columns = {'Day':'month'}, inplace = True)\n",
    "Month_data.set_index('month', inplace = True)\n",
    "Month_data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "MYPOR = portfolio_mom[['P1','Pmax','My_portfolio']]\n",
    "MYPOR = MYPOR.dropna()\n",
    "MYPOR = pd.merge(MYPOR,Month_data,left_index=True,right_index=True,how='left')\n",
    "MYPOR['month'] = pd.date_range(start = '1995',periods=len(MYPOR),freq = 'ME')\n",
    "MYPOR.set_index('month', inplace = True)\n",
    "MYPOR"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_port = smf.ols('My_portfolio ~ Raw_return',\n",
    "                 data=MYPOR['2000-01':'2023-12']).fit(\n",
    "                     cov_type='HAC', cov_kwds={'maxlags': 6})\n",
    "print(model_port.summary())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 结果解读"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这表示该策略有α平均每个月0.94%，该策略没有显著的β。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 计算投资组合的Sharpe Ratio\n",
    "sharpe_ratio = MYPOR['My_portfolio'].mean() / MYPOR['My_portfolio'].std() * np.sqrt(12)\n",
    "print(f\"Sharpe Ratio: {sharpe_ratio}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 还原投资策略的价格\n",
    "MYPOR['price_portfolio'] = (1 + MYPOR['My_portfolio']).cumprod()\n",
    "MYPOR['price_p1'] = (1 + MYPOR['P1']).cumprod()\n",
    "MYPOR['price_pmax'] = (1 + MYPOR['Pmax']).cumprod()\n",
    "MYPOR['price_market'] = (1 + MYPOR['Raw_return']).cumprod()\n",
    "MYPOR"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 画图\n",
    "fig = plt.figure(figsize=(12, 4))\n",
    "plt.plot(\n",
    "    'price_portfolio',  \n",
    "    '.-r', \n",
    "    label='Price of My Portfolio',  \n",
    "    linewidth=1, \n",
    "    data=MYPOR)  \n",
    "plt.title(\"China's Stock Market\") \n",
    "plt.xlabel('Month') \n",
    "plt.ylabel('Return') \n",
    "\n",
    "plt.plot(\n",
    "    'price_market', \n",
    "    '.-b', \n",
    "    label='Price of Market', \n",
    "    linewidth=1, \n",
    "    data=MYPOR) \n",
    "\n",
    "plt.plot(\n",
    "    'price_p1', \n",
    "    '.-g', \n",
    "    label='Price of Lowest', \n",
    "    linewidth=1, \n",
    "    data=MYPOR) \n",
    "\n",
    "plt.plot(\n",
    "    'price_pmax', \n",
    "    '.-c', \n",
    "    label='Price of Highest', \n",
    "    linewidth=1, \n",
    "    data=MYPOR) \n",
    "\n",
    "# plot legend\n",
    "plt.legend(loc='upper left')\n",
    "\n",
    "# 添加网格线\n",
    "plt.grid(True)\n",
    "\n",
    "# 添加横线 1\n",
    "plt.axhline(y=1, color='gray', linewidth=1)\n",
    "\n",
    "plt.show();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 计算累积收益率\n",
    "MYPOR['cumulative_return'] = (1 + MYPOR['My_portfolio']).cumprod()\n",
    "\n",
    "# 计算滚动最大值\n",
    "MYPOR['rolling_max'] = MYPOR['cumulative_return'].cummax()\n",
    "\n",
    "# 计算回撤\n",
    "MYPOR['drawdown'] = MYPOR['cumulative_return'] / MYPOR['rolling_max'] - 1\n",
    "\n",
    "# 计算最大回撤\n",
    "max_drawdown = MYPOR['drawdown'].min()\n",
    "print(f\"Maximum Drawdown: {max_drawdown}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "max_drawdown_end = MYPOR['drawdown'].idxmin() # 返回 Series 中最小值的索引\n",
    "max_drawdown_start = MYPOR.loc[:max_drawdown_end, 'cumulative_return'].idxmax() # 这部分代码选择了从数据开始到 max_drawdown_end 时间点之间的所有累积收益率\n",
    "\n",
    "print(f\"Maximum Drawdown: {max_drawdown}\")\n",
    "print(f\"Maximum Drawdown Start Date: {max_drawdown_start}\")\n",
    "print(f\"Maximum Drawdown End Date: {max_drawdown_end}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
